/**
 * 请求封装
 * @author luguoxiang
 * @date 2022-03-02
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
import { stringify } from 'qs';
import dayjs from 'dayjs';
import { createCache } from 'lg.cache';
import { getToken, loadToken } from '@/soar';
import message from '../message';
import { baseURL, tokenName } from '@/config/global';
import xhr, { XhrConfig } from './xhr';
import soarQuery from './soarQuery';
import { useAccountStore } from '@/store';

export interface HttpConfig extends XhrConfig {
  /**
   * 开启缓存，可直接配置字符串做为缓存key
   */
  cache?: string | boolean;
  /**
   * 开启防重锁，可直接配置字符串做为锁key
   */
  lock?: string | boolean;
  /**
   * 缓存有效期，单位：秒
   */
  cacheExpires?: number;
  /**
   * 响应值类型，
   *  raw：res
   *  result：res.data
   *  默认：res.data.data
   */
  returnType?: 'raw' | 'result';
  /**
   * 消息提示主题
   */
  title?: string;
  /**
   * 请求成功消息
   */
  successMsg?: string | boolean;
  /**
   * 请求失败消息
   */
  errorMsg?: string | boolean;
}

const cache = createCache('request');

/**
 * 创建实例
 */
const instance = xhr.create({
  baseURL,
  timeout: 30000,
  headers: { 'Content-Type': 'application/json;' },
});

/**
 * 添加过滤器（缓存处理）
 */
instance.addFilter((config: HttpConfig, next: any) => {
  if (!config.cache) {
    return next(config);
  }
  const key = typeof config.cache === 'string' ? config.cache : config.url;
  return cache.load(key, () => next(config), config.cacheExpires);
});

/**
 * 添加过滤器（锁定处理）
 */
const lockMap: { [k: string]: Promise<any> } = {};
instance.addFilter((config: HttpConfig, next: any) => {
  if (!config.lock) {
    return next(config);
  }
  const key = typeof config.lock === 'string' ? config.lock : config.url;
  if (!(key in lockMap)) {
    lockMap[key] = next(config);
    lockMap[key].finally(() => {
      delete lockMap[key];
    });
  }
  return lockMap[key];
});

/**
 * 请求拦截器（发其请求前）
 */
instance.requestInterceptor.use((config: HttpConfig) => {
  if (config.params && config.params.$soarQuery) {
    config.params = { ...soarQuery(config.params.$soarQuery), ...config.params };
    delete config.params.$soarQuery;
  }
  return {
    ...config,
    headers: {
      ...config.headers,
      [tokenName]: getToken(),
    },
  };
});

/**
 * 响应拦截器
 */
instance.responseInterceptor.use((res: any, config: HttpConfig) => {
  if (res.data.success === false) {
    const error: any = new Error(res.data.msg);
    error.response = res;
    throw error;
  }
  // 成功提示处理
  const { data } = res;
  if (config.successMsg) {
    message.success({
      title: `${config.title || '操作'}成功`,
      message: typeof config.successMsg === 'string' ? config.successMsg : data.msg || '您的操作已成功',
    });
  }
  // 返回值处理
  if (config.returnType === 'raw') {
    return res;
  }
  if (config.returnType === 'result') {
    return res.data;
  }
  return res.data.data;
});

/**
 * 请求失败处理
 * @param err
 * @param reject
 * @param config
 * @returns {*[]}
 */
function error(err: any, reject: Function, config: HttpConfig) {
  reject(err);
  if (!config.errorMsg) {
    return;
  }
  const res = err.response ?? {};
  const data = res.data ?? {};
  switch (res.status) {
    case 400:
      if (Array.isArray(data.errors)) {
        // 自行解析错误提示
        data.msg = data.errors.map((item: any) => item.defaultMessage).join('，');
      }
      break;
    case 404:
      data.msg = '访问资源不存在';
      break;
    case 502:
      data.msg = '服务器升级中';
      break;
    case 503:
      data.msg = '服务器升级中';
      break;
    default:
      break;
  }

  const errMsg = typeof config.errorMsg === 'string' ? config.errorMsg : data.msg || '请求出现错误，请稍后再试';
  message.error({
    title: `${config.title || '操作'}失败`,
    message: `code：${data.code || res.status}，${errMsg}`,
  });
}

function refreshToken({ resolve, reject, config }) {
  // 刷新token
  loadToken(true)
    .then(() => {
      // 成功处理
      instance.request(config).then(resolve).catch(reject);
    })
    .catch((err) => {
      // @ts-ignore
      useAccountStore()?.updateLoginModalVisible(true);
      // 失败处理
      error(err, reject, config);
    });
}

/**
 * 请求包装
 * @param config
 * @returns {Promise<unknown>}
 */
export default function request(config: HttpConfig) {
  return new Promise((resolve, reject) => {
    // 是否需等待（正在获取token），加入等待队列
    if (instance.todos) {
      instance.todos.push({ resolve, reject, config });
      return;
    }
    config.headers = { 'soar-response-pack-mode': 'pack', ...config.headers };
    // 发起请求
    instance
      .request(config)
      .then(resolve)
      .catch((err: any) => {
        // 请求失败
        const res = err.response;
        // 是否是token失效
        if (res?.data?.code === 'A0001') {
          refreshToken({ resolve, reject, config });
        } else {
          error(err, reject, config);
        }
      });
  });
}

/**
 * 基础路径
 */
request.baseURL = baseURL;

/**
 * get 请求
 * @param url
 * @param config
 */
request.get = (url: string, config?: HttpConfig): Promise<any> => {
  return request({ errorMsg: true, ...config, url, method: 'GET' });
};

/**
 * post 请求
 * @param url
 * @param config
 */
request.post = (url: string, config?: HttpConfig): Promise<any> => {
  return request({
    errorMsg: true,
    successMsg: true,
    ...config,
    url,
    method: 'POST',
  });
};

/**
 * put 请求
 * @param url
 * @param config
 */
request.put = (url: string, config?: HttpConfig): Promise<any> => {
  return request({
    errorMsg: true,
    successMsg: true,
    ...config,
    url,
    method: 'PUT',
  });
};

/**
 * delete 请求
 * @param url
 * @param config
 */
request.delete = (url: string, config?: HttpConfig): Promise<any> => {
  return request({
    errorMsg: true,
    successMsg: true,
    ...config,
    url,
    method: 'DELETE',
  });
};

interface UploadConfig extends HttpConfig {
  /**
   * 附件
   */
  files: File[] | File;
  /**
   * 附件文件名
   */
  filename?: string;
  /**
   * 后端接收附件的变量名
   */
  name?: string;
}

/**
 * 文件上传请求
 * @param url
 * @param config
 */
request.upload = (url: string, config: UploadConfig | File) => {
  if (config instanceof File) {
    config = { files: config };
  }
  const data = config.data || {};
  const formData = new FormData();
  const files = Array.isArray(config.files) ? config.files : [config.files];
  const name = config.name || 'files';
  for (const file of files) {
    formData.append(name, file, config.filename || file.name);
  }
  for (const k in data) {
    formData.append(k, data[k]);
  }
  return request({
    ...config,
    data: formData,
    headers: { 'Content-Type': 'multipart/form-data' },
    url,
    method: 'POST',
  });
};

/**
 * 下载
 * @param url
 * @param config
 * @param filename 文件名，非必填
 */
request.download = (url, config, filename?: string) => {
  return request({
    url,
    method: config.method || 'get',
    responseType: 'blob',
    ...config,
  }).then((response: any) => {
    if (!filename) {
      // 获取文件名称
      const disposition = response.headers['content-disposition'];
      const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
      const matches = filenameRegex.exec(disposition);
      filename = matches?.[1]?.replace(/['"]/g, '');
      filename = filename ? decodeURIComponent(filename) : `${dayjs().format('YYYY-MM-DD HH.mm.ss')}.xlsx`;
    }
    // 输出文件
    const link = document.createElement('a');
    link.href = window.URL.createObjectURL(response.data);
    link.download = filename;
    document.body.appendChild(link);
    link.click();
    // @ts-ignore
    URL.revokeObjectURL(blob);
    document.body.removeChild(link);
    return response;
  });
};

/**
 * 跳转
 * @param url
 * @param params
 */
request.href = async (url: string, params: any = {}) => {
  if (params.$soarQuery) {
    params = { ...soarQuery(params.$soarQuery), ...params };
    delete params.$soarQuery;
  }
  params[tokenName] = getToken();
  window.location.href = `${baseURL}${url}${url.indexOf('?') === -1 ? '?' : '&'}${stringify(params)}`;
};

/**
 * 打开页面
 * @param url
 * @param params
 */
request.open = (url: string, params: any = {}) => {
  if (params.$soarQuery) {
    params = { ...soarQuery(params.$soarQuery), ...params };
    delete params.$soarQuery;
  }
  params[tokenName] = getToken();
  window.open(`${baseURL}${url}${url.indexOf('?') === -1 ? '?' : '&'}${stringify(params)}`, '_blank');
};
